/**
 * File name: Graph.java Author: Willard Wang Create date: May 22, 2013
 */
package edu.sjtu.se.prophet.graph;

import java.io.Serializable;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;

import edu.sjtu.se.prophet.exception.EdgeExistsException;
import edu.sjtu.se.prophet.exception.EdgeNotFoundException;
import edu.sjtu.se.prophet.exception.NodeExistsException;
import edu.sjtu.se.prophet.exception.NodeNotFoundException;

/**
 * @author deathspeeder
 * 
 */
public class Graph implements Serializable {
    /**
     * 
     */
    private static final long serialVersionUID = 5779876224187012616L;
    protected Map<String, Node> nodes = new TreeMap<String, Node>();
    protected Set<Edge> edges = new TreeSet<Edge>();
    protected Map<String, Object> meta = new TreeMap<String, Object>();

    public Collection<Node> getNodes() {
        return nodes.values();
    }

    public Node findNode(String id) {
        if (nodes.containsKey(id))
            return nodes.get(id);
        else
            return null;
    }

    public Edge findEdge(String fromId, String toId) {
        Node start = findNode(fromId);
        if (start == null)
            return null;

        Node end = findNode(toId);
        if (end == null)
            return null;

        Edge temp = new Edge(start, end);
        if (!edges.contains(temp))
            return null;

        for (Edge edge : edges) {
            if (edge.equals(temp))
                return edge;
        }

        return null;
    }

    public boolean containsEdge(Node start, Node end) {
        return edges.contains(new Edge(start, end));
    }

    public void addNode(String id) throws NodeExistsException {
        if (!nodes.containsKey(id))
            nodes.put(id, new Node(id));
        else
            throw new NodeExistsException();
    }

    public void addNode(Node node) throws NodeExistsException {
        if (nodes.containsKey(node.getId()))
            throw new NodeExistsException();
        nodes.put(node.getId(), node);
    }

    public void removeNode(String id) throws NodeNotFoundException {
        if (!nodes.containsKey(id))
            throw new NodeNotFoundException(id);
        Node node = nodes.get(id);

        for (Node in : node.getInNodes()) {
            in.getOutNodes().remove(node);
        }

        for (Node out : node.getOutNodes()) {
            out.getInNodes().remove(node);
        }

        nodes.remove(id);
    }

    public void addEdge(String fromId, String toId)
            throws NodeNotFoundException, EdgeExistsException {

        if (!nodes.containsKey(fromId))
            throw new NodeNotFoundException(fromId);
        if (!nodes.containsKey(toId))
            throw new NodeNotFoundException(toId);

        Node from = nodes.get(fromId);
        Node to = nodes.get(toId);

        if (from.getOutNodes().contains(to))
            throw new EdgeExistsException();

        from.getOutNodes().add(to);
        to.getInNodes().add(from);
        addEdge(from, to);
    }

    public void removeEdge(String fromId, String toId)
            throws NodeNotFoundException, EdgeNotFoundException {

        if (!nodes.containsKey(fromId))
            throw new NodeNotFoundException(fromId);
        if (!nodes.containsKey(toId))
            throw new NodeNotFoundException(toId);

        Node from = nodes.get(fromId);
        Node to = nodes.get(toId);

        if (!from.getOutNodes().contains(to))
            throw new EdgeNotFoundException();

        from.getOutNodes().remove(to);
        to.getInNodes().remove(from);
        removeEdge(from, to);
    }

    public Set<Edge> getEdges() {
        return edges;
    }

    public Map<String, Object> getMeta() {
        return meta;
    }

    public void setMeta(Map<String, Object> meta) {
        this.meta = meta;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("META:\n");
        for (String key : meta.keySet()) {
            builder.append("\t" + key + ": " + meta.get(key) + "\n");
        }
        builder.append("NODE:\n");
        for (Node n : nodes.values()) {
            builder.append("\t" + n.toString() + "\n");
        }
        return builder.toString();
    }

    private void addEdge(Node start, Node end) throws EdgeExistsException {
        Edge edge = new Edge(start, end);
        if (edges.contains(edge))
            throw new EdgeExistsException();
        edges.add(edge);
    }

    private void removeEdge(Node start, Node end) throws EdgeNotFoundException {
        Edge edge = new Edge(start, end);
        if (!edges.contains(edge))
            throw new EdgeNotFoundException();
        edges.remove(edge);
    }
}
